#include <iostream>
#include <cstring>
#include <chrono>
#include <glm/mat4x4.hpp>
#include <glm/gtx/transform.hpp>
#include "VK_UniformBuffer.h"
#include "VK_Context.h"
#include "VK_Pipeline.h"
#include "VK_Image.h"
#include "VK_Texture.h"
#include "VK_DynamicState.h"
#include "VK_Window.h"
#include "VK_CommandPool.h"
#include "../Node.h"
#include "../ObjectFactory.h"

using namespace std;

const std::vector<float> vertices = {
    -1.0f, -1.0f,
    1.0f, -1.0f,
    1.0f,  1.0f,
    -1.0f,  1.0f
};

const std::vector<uint32_t> indices = {
    0, 1, 2, 2, 3, 0
};

struct UniformBufferObject {
    float iTime;
    float width;
    float height;
};

class fire : public Node
{
public:
    fire()
    {
        nodeName = "fire";
    };
public:
    RunningState run()override
    {
        auto window = VK_Window::getInstance()->getWindow();
        auto title = getNodeTitle();
        glfwSetWindowTitle(window, title.data());

        context = createVkContext();

        VK_Context::VK_Config vkConfig;
        bool ok = context->initVulkanDevice(vkConfig);
        if (!ok)
            return RunningState_Failed;

        context->setClearColor(0.0f, 0.8f, 0.2f, 0.0f);

        auto shaderSet = context->createShaderSet();
        shaderSet->addShader("../shader/fire/vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
        shaderSet->addShader("../shader/fire/frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);

        shaderSet->appendVertexAttributeDescription(0, sizeof(float) * 2, VK_FORMAT_R32G32_SFLOAT, 0);
        shaderSet->appendVertexInputBindingDescription(sizeof(float) * 2, 0, VK_VERTEX_INPUT_RATE_VERTEX);

        VkDescriptorSetLayoutBinding uniformBinding = VK_ShaderSet::createDescriptorSetLayoutBinding(0,
            VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT);
        shaderSet->addDescriptorSetLayoutBinding(uniformBinding);

        if (!shaderSet->isValid()) {
            std::cerr << "invalid shaderSet" << std::endl;
            shaderSet->release();
            context->release();
            return RunningState_Failed;
        }

        auto ubo = shaderSet->addUniformBuffer(0, sizeof(UniformBufferObject));
        ubo->setWriteDataCallback([this](char *&data, uint32_t size)->uint32_t{return updateUniformBufferData(data, size);});

        context->initVulkanContext();

        pipeline = context->createPipeline(shaderSet);
        pipeline->create();

        auto buffer = context->createVertexBuffer(vertices, 2, indices);
        pipeline->addRenderBuffer(buffer);

        context->createCommandBuffers();

        auto result = context->run();
        setAverageFps(context->getFps());
        context->release();
        return result;
    }

    uint32_t updateUniformBufferData(char *&data, uint32_t size)
    {
        static auto startTime = std::chrono::high_resolution_clock::now();
        auto currentTime = std::chrono::high_resolution_clock::now();
        float time = std::chrono::duration<float, std::chrono::seconds::period>
                     (currentTime - startTime).count();

        UniformBufferObject ubo = {};
        ubo.iTime = time;
        ubo.width = context->getSwapChainExtent().width;
        ubo.height = context->getSwapChainExtent().height;

        memcpy(data, &ubo, size);
        return sizeof(ubo);
    }
private:
    VK_Context *context = nullptr;
    VK_Pipeline *pipeline = nullptr;
};

REGISTER_OBJECT(Node, fire)
